using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;

namespace Charisma.Metadata
{
    public sealed class ConfiguredMetaDataProvider : MetaDataProvider
    {
        private readonly Func<Type, TypeConfiguration> configurationFinder;
        private readonly ConcurrentDictionary<Type, object> types = new ConcurrentDictionary<Type, object>(2, 8);

        public ConfiguredMetaDataProvider(DbModel dbModel)
        {
            Contract.Requires(dbModel != null);
            Contract.Ensures(configurationFinder != null);

            configurationFinder = t => dbModel.FindTypeConfiguration(t);
        }

        public ConfiguredMetaDataProvider(TypeConfigurationCollection configurations)
        {
            Contract.Requires(configurations != null);
            Contract.Ensures(configurationFinder != null);

            configurationFinder = t => configurations.Find(t);
        }

        public override MetaType GetMetaType(Type type)
        {
            TypeConfiguration configuration = FindTypeConfiguration(type);

            if (configuration == null)
                return base.GetMetaType(type);

            return (MetaType) types.GetOrAdd(type, _ => BuildMetaType(configuration));
        }

        private TypeConfiguration FindTypeConfiguration(Type type)
        {
            Contract.Requires(type != null);
            Contract.Requires(!type.IsInterface);
            Contract.Requires(!type.IsAbstract);
            Contract.Requires(!type.IsGenericTypeDefinition);
            Contract.Requires(!type.IsGenericParameter);

            return configurationFinder(type);
        }

        private static MetaType BuildMetaType(TypeConfiguration typeConfiguration)
        {
            Contract.Requires(typeConfiguration != null);

            List<FieldConfiguration> fieldConfigurations = typeConfiguration.Fields
                .Where(pc => !pc.IsIgnored)
                .ToList();
            FieldConfiguration keyProperty = fieldConfigurations.SingleOrDefault(pc => pc.IsKey);
            Dictionary<string, MetaFieldDescription> fields = fieldConfigurations
                .ToDictionary(
                    pc => pc.Name.ToLowerInvariant(),
                    pc =>
                    new MetaFieldDescription(pc.Name, pc.Alias, pc.Field)
                        {CollectionInitializer = pc.CollectionInitializer});

            return BuildMetaTypeInternal(
                typeConfiguration.Type,
                fields,
                null, // TODO: fill materializationHandler
                s => s == (keyProperty == null ? null : keyProperty.Name),
                FilterFields);
        }

        private static bool FilterFields(MemberInfo mi)
        {
            var pi = mi as PropertyInfo;
            if (pi != null)
                return pi.GetGetMethod() != null && pi.GetSetMethod(true) != null && pi.GetIndexParameters().Length == 0;
            return true;
        }
    }
}